package com.cloud.tool.aop;

import com.cloud.tool.annotation.LimitMethod;
import com.cloud.tool.exception.ToolException;
import com.cloud.tool.service.LuaTool;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * @Author WangZY
 * @Date 2022/2/21 17:21
 * @Description 基于漏桶思想的限流器
 **/
@Aspect
@Component
@Slf4j
public class LimitHandler {

    @Autowired
    private LuaTool luaTool;
    @Autowired
    private ConfigurableEnvironment config;

    @Pointcut("@annotation(com.cloud.tool.annotation.LimitMethod)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        LimitMethod limitMethod = methodSignature.getMethod().getAnnotation(LimitMethod.class);
        int limit = limitMethod.limit();
        String application = config.getProperty("spring.application.name");
        String methodName = methodSignature.getName();
        String key = "";
        if (StringUtils.isEmpty(application)) {
            throw new ToolException("当前项目必须拥有spring.application.name才能使用限流器");
        } else {
            key = application + ":limit:" + methodName;
        }
        long judgeLimit = luaTool.judgeLimit(key, limit);
        if (judgeLimit == -1) {
            throw new ToolException("系统同时允许执行最多" + limit + "次当前方法");
        } else {
            log.info(methodSignature.getDeclaringTypeName() + "." + methodName + "在系统中允许同时执行" + limit +
                    "次当前方法，当前执行中的有" + judgeLimit + "个");
            Object[] objects = joinPoint.getArgs();
            return joinPoint.proceed(objects);
        }
    }

    /**
     * spring4/springboot1：
     * 正常：@Around-@Before-method-@Around-@After-@AfterReturning
     * 异常：@Around-@Before-@After-@AfterThrowing
     * spring5/springboot2：
     * 正常：@Around-@Before-method-@AfterReturning-@After-@Around
     * 异常：@Around-@Before-@AfterThrowing-@After
     */
    @After("pointCut()")
    public void after(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        LimitMethod limitMethod = methodSignature.getMethod().getAnnotation(LimitMethod.class);
        int limit = limitMethod.limit();
        String application = config.getProperty("spring.application.name");
        String methodName = methodSignature.getName();
        if (StringUtils.hasText(application)) {
            String key = application + ":limit:" + methodName;
            long nowCount = luaTool.returnCount(key);
            log.info(methodSignature.getDeclaringTypeName() + "." + methodName + "在系统中允许同时执行最多" + limit +
                    "次当前方法，执行完毕后返还次数，现仍执行中的有" + nowCount + "个");
        }
    }
}
